package uk.ac.ebi.uniprot.wikipedia.entryProcessing;

import org.apache.log4j.Logger;
import org.json.JSONException;
import uk.ac.ebi.kraken.interfaces.uniprot.DatabaseType;
import uk.ac.ebi.kraken.interfaces.uniprot.UniProtAccession;
import uk.ac.ebi.kraken.interfaces.uniprot.UniProtEntry;
import uk.ac.ebi.kraken.interfaces.uniprot.UniProtEntryType;
import uk.ac.ebi.kraken.interfaces.uniprot.dbx.geneid.GeneId;
import uk.ac.ebi.uniprot.wikipedia.exceptions.NoSuchOrganismException;
import uk.ac.ebi.uniprot.wikipedia.exceptions.RetrievalException;
import uk.ac.ebi.uniprot.wikipedia.exceptions.ServiceException;
import uk.ac.ebi.uniprot.wikipedia.exceptions.UnexpectedException;
import uk.ac.ebi.uniprot.wikipedia.model.GeneWikiPage;
import uk.ac.ebi.uniprot.wikipedia.retrievalServices.GeneWikiRetrieval;
import uk.ac.ebi.uniprot.wikipedia.retrievalServices.UniProtRetrievalImpl;
import uk.ac.ebi.uniprot.wikipedia.util.GeneWikiOrganism;

import java.io.IOException;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * User: ljgarcia
 * Date: 03/07/12
 * Time: 11:12
 * To change this template use File | Settings | File Templates.
 */
public abstract class GeneWikiXRefConverter {
    protected Logger logger = org.apache.log4j.Logger.getLogger(GeneWikiXRefConverter.class);
    protected Set<GeneWikiPage> withAccession;
    protected Set<GeneWikiPage> noAccession;
    protected HashMap<UniProtAccession, Collection<GeneId>> mapping;
    protected GeneWikiOrganism organism;

    /**
     * The reported triplet gene-wiki-page, gene-id, acc matches to a pair gene-id, acc in UniProt.
     */
    protected Set<GeneWikiPage> matchingGeneWikiEntries;
    /**
     * The reported triplet gene-wiki-page, gene-id, null is missing an existing accession,
     * ie a UniProt reviewed accession exists for that gene but is not reported.
     */
    protected Set<GeneWikiPage> missingReviewedAccession;
    /**
     * The reported triplet gene-wiki-page, gene-id, up-acc contains a non-reviewed accession,
     * ie the reported accession is a TrEMBL one.
     */
    protected Set<GeneWikiPage> notReviewedAccession;
    /**
     * The reported triplet gene-wiki-page, gene-id, up-acc contains an accession without known genes,
     * ie there is not recorded genes for that accession.
     */
    protected Set<GeneWikiPage> noGeneForAccession;
    /**
     * The reported triplet gene-wiki-page, gene-id, up-acc contains a gene that is not recognized for that accession,
     * ie non of the recorded genes for that accession matches with the reported one.
     */
    protected Set<GeneWikiPage> wrongGeneForAccession;
    /**
     * The reported triplet gene-wiki-page, gene-id, up-acc contains a up-acc whose organism does not correspond to the
     * one managed by the implementing class.
     */
    protected Set<GeneWikiPage> wrongOrganismForAccession;
    /**
     * The reported triplet gene-wiki-page, gene-id, up-acc contains an undefined organism
     */
    protected Set<GeneWikiPage> undefinedOrganismForAccession;
    /**
     * Entry retrieval failed for these gene wiki pages, we do not know whether or not it can be a candidate, check logs.
     */
    protected Set<GeneWikiPage> unexpectedError;
    /**
     * Has the set with missing reviewed accessions for those GeneWiki pages without an accession already generated?
     */
    private boolean alreadySetMissingReviewedAccession = false;
    /**
     * Have the sets for those GeneWiki pages with an accession already generated?
     */
    private boolean alreadySetWithAccession = false;
    /**
     * dependency on other beans
     */
    protected UniProtRetrievalImpl uniProtRetrievalService;
    protected GeneWikiRetrieval genewikiRetrieval;

    /**
     * Init method.
     */
    protected void init () throws IOException, JSONException, ServiceException {
        this.withAccession = new TreeSet<GeneWikiPage>();
        this.noAccession = new TreeSet<GeneWikiPage>();
        this.mapping = new HashMap<UniProtAccession, Collection<GeneId>>();
        this.matchingGeneWikiEntries = new TreeSet<GeneWikiPage>();
        this.missingReviewedAccession = new TreeSet<GeneWikiPage>();
        this.noGeneForAccession = new TreeSet<GeneWikiPage>();
        this.notReviewedAccession = new TreeSet<GeneWikiPage>();
        this.wrongGeneForAccession = new TreeSet<GeneWikiPage>();
        this.wrongOrganismForAccession = new TreeSet<GeneWikiPage>();
        this.undefinedOrganismForAccession = new TreeSet<GeneWikiPage>();
        this.unexpectedError = new TreeSet<GeneWikiPage>();
    }

    /**
     * Destroys the bean, cleans all collections.
     */
    public void destroy() {
        this.mapping.clear();
        this.matchingGeneWikiEntries.clear();
        this.missingReviewedAccession.clear();
        this.noAccession.clear();
        this.noGeneForAccession.clear();
        this.notReviewedAccession.clear();
        this.withAccession.clear();
        this.wrongGeneForAccession.clear();
        this.wrongOrganismForAccession.clear();
        this.undefinedOrganismForAccession.clear();
        this.unexpectedError.clear();
    }

    /**
     * Init collections related to wiki pages with accessions: (i) set with non reviewed accessions --to be reported to wiki,
     * (ii) set with reviewed accession but non matching gene --to be reported to wiki and curators, and
     * (iii) set with a matching gene --to be reported as xref candidate.
     * @throws IOException
     * @throws JSONException
     * @throws ServiceException
     */
    private void initXRefsWithAccession() throws IOException, JSONException, ServiceException {
        if (!this.alreadySetWithAccession) {
            for (GeneWikiPage page: this.genewikiRetrieval.getGeneWikiWithAccession()) {
                UniProtEntry entry = null;
                try {
                    entry = this.uniProtRetrievalService.getEntryByAccessionAndOrganism(page.getAccession(), this.getOrganism());
                    if (entry.getType().equals(UniProtEntryType.SWISSPROT)) {
                        List<GeneId> genes = entry.getDatabaseCrossReferences(DatabaseType.GENEID);
                        if (genes.size() == 0) { //no genes for that accession, are we missing tha gene or is a wrong enty in the wiki?
                            this.noGeneForAccession.add(page);
                        } else {
                            boolean found = false;
                            for (GeneId gene:genes) {
                                if (page.getGeneId().equals(gene.getGeneIdAccessionNumber().getValue())) {
                                    found = true;
                                    break;
                                }
                            }
                            if (found) { //one of the genes is a match!
                                this.matchingGeneWikiEntries.add(page);
                            } else {
                                this.wrongGeneForAccession.add(page);
                            }
                        }
                    } else { //is not a reviewed entry, should not be used in wikis
                        this.notReviewedAccession.add(page);
                    }
                } catch (NoSuchOrganismException e) {
                    this.wrongOrganismForAccession.add(page);
                } catch (UnexpectedException e) {
                    logger.error(e.getMessage());
                    this.unexpectedError.add(page);
                } catch (RetrievalException e) {
                    this.undefinedOrganismForAccession.add(page);
                }
            }
            this.alreadySetWithAccession = true;
        }
    }

    /**
     * Retrieves all the pages originally with no UniProt accession for which genes there is indeed a reviewed
     * accession in our data bases. If more than one accession is available for that gene, only the one is taken
     * into account.
     * @return
     * @throws IOException
     * @throws JSONException
     * @throws ServiceException
     */
    public Set<GeneWikiPage> getMissingReviewedAccession() throws IOException, JSONException, ServiceException {
        if (!this.alreadySetMissingReviewedAccession) {
            for (GeneWikiPage page: this.genewikiRetrieval.getGeneWikiNoAccession()) {
                List<UniProtEntry> entries = this.uniProtRetrievalService.getEntryByGeneIdAndOrganism(page.getGeneId(), this.organism, true);
                if (entries.size() > 0) {
                    GeneWikiPage toReview = new GeneWikiPage();
                    toReview.setWikiURL(page.getWikiURL());
                    toReview.setGeneId(page.getGeneId());
                    toReview.setAccession(entries.get(0).getPrimaryUniProtAccession().getValue());
                    this.missingReviewedAccession.add(toReview);
                }
            }
            this.alreadySetMissingReviewedAccession = true;
        }
        return this.missingReviewedAccession;
    }

    public Set<GeneWikiPage> getMatchingGeneWikiEntries() throws IOException, JSONException, ServiceException {
        this.initXRefsWithAccession();
        return this.matchingGeneWikiEntries;
    }

    public Set<GeneWikiPage> getUndefinedOrganismForAccession() throws IOException, JSONException, ServiceException {
        this.initXRefsWithAccession();
        return this.undefinedOrganismForAccession;
    }

    public Set<GeneWikiPage> getUnexpectedError() throws IOException, JSONException, ServiceException {
        this.initXRefsWithAccession();
        return this.unexpectedError;
    }

    public Set<GeneWikiPage> getWrongOrganismForAccession() throws IOException, JSONException, ServiceException {
        this.initXRefsWithAccession();
        return this.wrongOrganismForAccession;
    }

    public Set<GeneWikiPage> getNotReviewedAccession() throws IOException, JSONException, ServiceException {
        this.initXRefsWithAccession();
        return this.notReviewedAccession;
    }

    public Set<GeneWikiPage> getNoGeneForAccession() throws IOException, JSONException, ServiceException {
        this.initXRefsWithAccession();
        return this.noGeneForAccession;
    }

    public Set<GeneWikiPage> getWrongGeneForAccession() throws IOException, JSONException, ServiceException {
        this.initXRefsWithAccession();
        return this.wrongGeneForAccession;
    }

    public void setUniProtRetrievalService(UniProtRetrievalImpl uniProtRetrievalService) {
        this.uniProtRetrievalService = uniProtRetrievalService;
    }

    public void setGenewikiRetrieval(GeneWikiRetrieval genewikiRetrieval) {
        this.genewikiRetrieval = genewikiRetrieval;
    }

    public abstract void setOrganism();

    protected void setOrganism(GeneWikiOrganism organism) {
        this.organism = organism;
    }

    public UniProtRetrievalImpl  getUniProtRetrievalService() {
        return this.uniProtRetrievalService;
    }

    public GeneWikiRetrieval getGenewikiRetrieval() {
        return this.genewikiRetrieval;
    }

    public GeneWikiOrganism getOrganism() {
        return this.organism;
    }
}
